/*******************************************************************************
 * Jimm - Mobile Messaging - J2ME ICQ clone
 * Copyright (C) 2003-05  Jimm Project
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 ********************************************************************************
 * File: src/jimm/comm/SendMessageAction.java
 * Version: ###VERSION###  Date: ###DATE###
 * Author(s): Manuel Linsmayer, Spassky Alexander, Andreas Rossbacher
 *******************************************************************************/


// #sijapp cond.if protocols_ICQ is "true" #
package org.jimm.protocol.icq.action;

import org.jimm.app.JimmApplication;
import org.jimm.app.JimmException;
import org.jimm.comm.*;

import org.jimm.message.Message;
import org.jimm.message.PlainMessage;
import org.jimm.message.PluginMessage;
import org.jimm.protocol.icq.*;
import org.jimm.protocol.icq.packet.*;
import org.jimm.protocol.icq.plugin.*;
import org.jimm.service.Preferences;


public class SendMessageAction extends IcqAction {
    // Static variables for message type;
    public static final int MESSAGE_TYPE_AUTO = 0x0000;
    public static final int MESSAGE_TYPE_NORM = 0x0001;
    public static final int MESSAGE_TYPE_EXTENDED = 0x001a;
    public static final int MESSAGE_TYPE_AWAY = 0x03e8;
    public static final int MESSAGE_TYPE_OCC = 0x03e9;
    public static final int MESSAGE_TYPE_NA = 0x03ea;
    public static final int MESSAGE_TYPE_DND = 0x03eb;
    public static final int MESSAGE_TYPE_FFC = 0x03ec;

    //    public static final int MESSAGE_TYPE_UNKNOWN  = 0x0000; // Unknown message, only used internally by this plugin
    public static final int MESSAGE_TYPE_PLAIN = 0x0001; // Plain text (simple) message
    //    public static final int MESSAGE_TYPE_CHAT     = 0x0002; // Chat request message
    public static final int MESSAGE_TYPE_FILEREQ = 0x0003; // File request / file ok message
    public static final int MESSAGE_TYPE_URL = 0x0004; // URL message (0xFE formatted)
    //    public static final int MESSAGE_TYPE_AUTHREQ  = 0x0006; // Authorization request message (0xFE formatted)
//    public static final int MESSAGE_TYPE_AUTHDENY = 0x0007; // Authorization denied message (0xFE formatted)
//    public static final int MESSAGE_TYPE_AUTHOK   = 0x0008; // Authorization given message (empty)
//    public static final int MESSAGE_TYPE_SERVER   = 0x0009; // Message from OSCAR server (0xFE formatted)
    public static final int MESSAGE_TYPE_ADDED = 0x000C; // "You-were-added" message (0xFE formatted)
    //    public static final int MESSAGE_TYPE_WWP      = 0x000D; // Web pager message (0xFE formatted)
//    public static final int MESSAGE_TYPE_EEXPRESS = 0x000E; // Email express message (0xFE formatted)
//    public static final int MESSAGE_TYPE_CONTACTS = 0x0013; // Contact list message
    public static final int MESSAGE_TYPE_PLUGIN = 0x001A; // Plugin message described by text string
//    public static final int MESSAGE_TYPE_AWAY     = 0x03E8; // Auto away message
//    public static final int MESSAGE_TYPE_OCC      = 0x03E9; // Auto occupied message
//    public static final int MESSAGE_TYPE_NA       = 0x03EA; // Auto not available message
//    public static final int MESSAGE_TYPE_DND      = 0x03EB; // Auto do not disturb message
//    public static final int MESSAGE_TYPE_FFC      = 0x03EC; // Auto free for chat message

    // Arrays for new capability blowup
    private static final byte[] CAP_UTF8_GUID = "{0946134E-4C7F-11D1-8222-444553540000}".getBytes();
    private static final byte[] CAP_ASCII_GUID = {};
    private byte[] encodingGUID = null;

    // Plain message
    private PlainMessage plainMsg = null;

    // receiver object
    private IcqContact rcvr;
    // encoding
    private boolean ucs2;

    private long time;
    private int SEQ1 = 0xffff;
    private Preferences p = JimmApplication.getInstance().getPreferences();

    // Constructor
    public SendMessageAction(Message msg) {
        super();
        if (msg instanceof PlainMessage) {
            this.plainMsg = (PlainMessage) msg;
        }
        rcvr = (IcqContact) msg.getRcvr();
    }

    // Init action
    public void init() throws JimmException {
        time = System.currentTimeMillis();
        // Forward init request depending on message type
        SEQ1--;
        if ((plainMsg instanceof PluginMessage)
                && !((PluginMessage) plainMsg).getPlugin().isAck()) {
            initAckMsg();
        } else {
            initPlainMsg();
        }
    }


    private void initPlainMsg() throws JimmException {
        int type = 1;
        Client client = rcvr.getClient();

        // What message format/encoding should we use?
        ucs2 = client.hasCapability(Client.CAPF_UTF8_INTERNAL);

        int messageType = getMessageType();
        if ((plainMsg != null)
                && (SendMessageAction.MESSAGE_TYPE_AWAY <= messageType)
                && (messageType <= SendMessageAction.MESSAGE_TYPE_FFC)) {
            type = 2;
            ucs2 = false;

        } else if (plainMsg instanceof PluginMessage) {
            type = 2;

        } else if (p.deliveryNotification
                && rcvr.isOnline()
                && client.hasCapability(Client.CAPF_DELIVERY)) {
            type = 2;
            notifyType = PlainMessage.NOTIFY_NONE;
        }


        if (1 == type) {
            notifyType = PlainMessage.NOTIFY_NONE;
        }

        if (2 == type) {
            encodingGUID = ucs2 ? CAP_UTF8_GUID : CAP_ASCII_GUID;
        }

        byte[] buf = (2 == type) ? channel2Data() : channel1Data();
        // Send packet
        sendPacket(new SnacPacket(SnacPacket.CLI_ICBM_FAMILY, SnacPacket.CLI_SENDMSG_COMMAND, buf));

        SEQ1--;
    }

    private byte[] channel1Data() throws JimmException {
        // Get UIN
        byte[] uinRaw = StringConvertor.stringToByteArray(rcvr.getUserId());

        byte[] textRaw = getMessageText();

        Util buffer = new Util();

        buffer.writeDWordBE(time); // CLI_SENDMSG.TIME
        buffer.writeDWordBE(0x00000000); // CLI_SENDMSG.ID
        buffer.writeWordBE(0x0001); // CLI_SENDMSG.FORMAT
        buffer.writeByte(uinRaw.length); // CLI_SENDMSG.UIN
        buffer.writeByteArray(uinRaw);

        buffer.writeWordBE(0x0002); // CLI_SENDMSG.SUB_MSG_TYPE1
        buffer.writeWordBE((ucs2 ? 6 : 5) + 4 + 4 + textRaw.length);
        buffer.writeWordBE(0x0501); // SUB_MSG_TYPE1.CAPABILITIES
        if (ucs2) {
            buffer.writeWordBE(0x0002);
            buffer.writeWordBE(0x0106);
        } else {
            buffer.writeWordBE(0x0001);
            buffer.writeByte(0x01);
        }
        buffer.writeWordBE(0x0101); // SUB_MSG_TYPE1.MESSAGE
        buffer.writeWordBE(4 + textRaw.length);
        buffer.writeDWordBE(ucs2 ? 0x00020000 : 0x00000000); // MESSAGE.ENCODING
        buffer.writeByteArray(textRaw); // MESSAGE.MESSAGE

        // store message if recipient offline
        buffer.writeTLV(0x0006, null);

        // Send packet
        return buffer.toByteArray();
    }

    private byte[] getMessageText() {
        String text = StringConvertor.restoreCrLf(plainMsg.getText());
        if (encodingGUID == CAP_UTF8_GUID) {
            return StringConvertor.stringToByteArrayUtf8(text);
        }
        if (encodingGUID == CAP_ASCII_GUID) {
            return StringConvertor.stringToByteArray(text);
        }
        return ucs2
                ? StringConvertor.stringToUcs2beByteArray(text)
                : StringConvertor.stringToByteArray(text);
    }

    private byte[] makeTlv5(byte[] textRaw, byte[] pluginData) {
        Util buffer = new Util();
        buffer.writeWordBE(0x0000);
        buffer.writeDWordBE(time);
        buffer.writeDWordBE(0x00000000);
        // SUB_MSG_TYPE2.CAPABILITY
        buffer.writeByteArray(GUID.CAP_AIM_SERVERRELAY.toByteArray());

        // Set TLV 0x0a to 0x0001
        buffer.writeTLVWord(0x000a, 0x0001);

        // Set emtpy TLV 0x0f
        buffer.writeTLV(0x000f, null);

        // Set TLV 0x2711
        buffer.writeTLV(0x2711, makeTlv1127(textRaw, pluginData));
        return buffer.toByteArray();
    }

    private byte[] channel2Data() throws JimmException {
        // Get UIN
        byte[] uinRaw = StringConvertor.stringToByteArray(rcvr.getUserId());

        // Get text
        byte[] textRaw = getMessageText();

        byte[] pluginData = null;
        if (plainMsg instanceof PluginMessage) {
            pluginData = pluginData();
        }

        // Build the packet
        Util buffer = new Util();

        buffer.writeDWordBE(time); // CLI_SENDMSG.TIME
        buffer.writeDWordBE(0x00000000); // CLI_SENDMSG.ID
        buffer.writeWordBE(0x0002); // CLI_SENDMSG.FORMAT
        buffer.writeByte(uinRaw.length); // CLI_SENDMSG.UIN
        buffer.writeByteArray(uinRaw);

        //-----------------TYPE2 Specific Data-------------------
        buffer.writeTLV(0x0005, makeTlv5(textRaw, pluginData));
        buffer.writeTLV(0x0003, null); // CLI_SENDMSG.UNKNOWN
        return buffer.toByteArray();
    }

    private void initAckMsg() throws JimmException {
        // Get UIN
        XtrazMessagePlugin plugin = ((PluginMessage) plainMsg).getPlugin();
        byte[] uinRaw = StringConvertor.stringToByteArray(rcvr.getUserId());
        encodingGUID = CAP_UTF8_GUID;
        byte[] textRaw = getMessageText();
        byte[] pluginData = pluginData();

        Util buffer = new Util();

        buffer.writeDWordBE(plugin.getCookie1()); // CLI_SENDMSG.TIME
        buffer.writeDWordBE(plugin.getCookie2()); // CLI_SENDMSG.ID
        buffer.writeWordBE(0x0002); // CLI_SENDMSG.FORMAT
        buffer.writeByte(uinRaw.length); // CLI_SENDMSG.UIN
        buffer.writeByteArray(uinRaw);
        buffer.writeWordBE(0x0003); // CLI_SENDMSG.FORMAT
        buffer.writeByteArray(makeTlv1127(textRaw, pluginData));

        sendPacket(new SnacPacket(SnacPacket.CLI_ICBM_FAMILY, SnacPacket.CLI_ACKMSG_COMMAND,
                buffer.toByteArray()));
    }

    private int getMessageType() {
        if (plainMsg instanceof PluginMessage) {
            return SendMessageAction.MESSAGE_TYPE_EXTENDED;
        }
        return SendMessageAction.MESSAGE_TYPE_NORM;
    }

    private byte[] makeTlv1127(byte[] textRaw, byte[] pluginData) {
        Util buffer = new Util();

        // Put 0x1b00
        buffer.writeWordLE(0x001B); // length

        // Put ICQ protocol version in LE
        buffer.writeWordLE(0x0008);

        // Put capablilty (16 zero bytes)
        buffer.writeDWordBE(0x00000000);
        buffer.writeDWordBE(0x00000000);
        buffer.writeDWordBE(0x00000000);
        buffer.writeDWordBE(0x00000000);

        // Put some unknown stuff
        buffer.writeWordLE(0x0000);
        buffer.writeByte(0x03);

        // Set the DC_TYPE to "normal" if we send a file transfer request
        buffer.writeDWordBE(0x00000000);

        // Put cookie, unkown 0x0e00 and cookie again
        buffer.writeWordLE(SEQ1);
        buffer.writeWordLE(0x000E); // length
        buffer.writeWordLE(SEQ1);

        // Put 12 unknown zero bytes
        buffer.writeDWordLE(0x00000000);
        buffer.writeDWordLE(0x00000000);
        buffer.writeDWordLE(0x00000000);

        // Put message type 0x0001 if normal message else 0x001a for file request
        buffer.writeWordLE(getMessageType());

        // Put contact status
        int onlineStatus = IcqStatusInfo.getExtStatus(getIcq().getProfile().statusIndex);
        buffer.writeWordLE(onlineStatus);

        // Put priority
        buffer.writeWordLE(0x0001);

        // Put message
        buffer.writeWordLE(textRaw.length + 1);
        buffer.writeByteArray(textRaw);
        buffer.writeByte(0x00);

        if (pluginData != null) {
            buffer.writeByteArray(pluginData);
        } else {

            // Put foreground, background color and guidlength
            buffer.writeDWordBE(0x00000000);
            buffer.writeDWordBE(0x00FFFFFF);

            buffer.writeDWordLE(encodingGUID.length);
            buffer.writeByteArray(encodingGUID);
            // SUB_MSG_TYPE2.CAPABILITY
        }

        return buffer.toByteArray();
    }

    private byte[] pluginData() {
        XtrazMessagePlugin plugin = ((PluginMessage) plainMsg).getPlugin();

        byte[] subType = StringConvertor.stringToByteArray(plugin.getSubTypeText());
        byte[] data = plugin.getData();
        GUID guid = plugin.getGUID();
        int command = plugin.getCommand();
        return pluginData(subType, data, guid, command, 0x00000000);
    }

    private byte[] pluginData(byte[] subType, byte[] data, GUID guid, int cmd, int flag2) {
        int headerLen = 16 + 2 + 4 + subType.length + 4 + 4 + 4 + 2 + 1;
        Util buffer = new Util();

        buffer.writeWordLE(headerLen);

        buffer.writeByteArray(guid.toByteArray());
        buffer.writeWordLE(cmd);

        // plugin name
        buffer.writeDWordLE(subType.length);
        buffer.writeByteArray(subType);

        buffer.writeDWordBE(0x00000100);
        buffer.writeDWordBE(flag2);
        buffer.writeDWordBE(0x00000000);
        buffer.writeWordBE(0x0000);
        buffer.writeByte(0x00);


        buffer.writeDWordLE(data.length);
        buffer.writeByteArray(data);

        return buffer.toByteArray();
    }

    private int notifyType = PlainMessage.NOTIFY_OFF;

    // Forwards received packet, returns true if packet was consumed
    public boolean forward(Packet packet) throws JimmException {
        if (PlainMessage.NOTIFY_OFF == notifyType) {
            return false;
        }
        if (!(packet instanceof SnacPacket)) {
            return false;
        }
        SnacPacket snac = (SnacPacket) packet;
        if (SnacPacket.CLI_ICBM_FAMILY != snac.getFamily()) {
            return false;
        }
        if ((snac.getCommand() != 0x000C) && (snac.getCommand() != 0x000B)) {
            return false;
        }

        byte[] buf = snac.getData();
        long msgId = Util.getDWordBE(buf, 0) & 0xFFFFFFFFL;
        if (msgId != (time & 0xFFFFFFFFL)) {
            return false;
        }
        String uin = StringConvertor.byteArrayToWinString(buf, 11, buf[10]);
        boolean toClient = (snac.getCommand() != 0x000C);
        if (rcvr.getUserId().equals(uin)) {
            notifyType = toClient ? PlainMessage.NOTIFY_FROM_CLIENT : PlainMessage.NOTIFY_FROM_SERVER;
            plainMsg.setSendingState(notifyType);
            return true;
        }
        return false;
    }


    // Returns true if the action is completed
    private static final int SEND_MESSSAGE_TIMEOUT = 5 * 60 * 1000;

    public boolean isCompleted() {
        if (PlainMessage.NOTIFY_OFF == notifyType) {
            return true;
        }
        if (System.currentTimeMillis() < (time + SEND_MESSSAGE_TIMEOUT)) {
            // #sijapp cond.if modules_DEBUGLOG is "true"#
            // if (PlainMessage.NOTIFY_NONE == notifyType) {
            //   DebugLog.println("Cant send message to " + rcvr.getName() + " " + rcvr.getUserId());
            //}
            // #sijapp cond.end#
            return (PlainMessage.NOTIFY_FROM_CLIENT == notifyType);
        }
        return true;
    }


    // Returns true if an error has occured
    public boolean isError() {
        return false;
    }
}
// #sijapp cond.end #
